package study.lagou.com.sqlSession;

import study.lagou.com.pojo.Configuration;
import study.lagou.com.pojo.MappedStatement;

import java.lang.reflect.*;
import java.util.List;

/**
 * @Description: 功能描述
 * @Author houjh
 * @Email: happyxiaohou@gmail.com
 * @Date: 2021-2-22 21:24
 */
public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

    private Executor simpleExecutor = new SimpleExecutor();

    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <E> List<E> selectList(String statementId, Object... params) throws Exception {
        List<Object> query = simpleExecutor.query(configuration, getMappedStatement(statementId), params);
        return (List<E>) query;
    }

    @Override
    public <T> T selectOne(String statementId, Object... params) throws Exception {
        //selectOne方法和selectList方法操作数据库的方式一致，所以我们此处直接调用selectList方法即可
        List<Object> objects = selectList(statementId, params);
        if(objects.size() == 1){
            return (T) objects.get(0);
        } else {
            throw new RuntimeException("查询到的结果集为空或返回的结果集较多!");
        }
    }

    @Override
    public Integer delete(String statementId, Object... params) throws Exception {
        return this.update(statementId,params);
    }

    @Override
    public Integer update(String statementId, Object... params) throws Exception {
        Integer updateNum = simpleExecutor.update(configuration, getMappedStatement(statementId), params);
        return updateNum;
    }

    /**
     * 通过statementId获取MappedStatement
     * @param statementId
     * @return
     */
    private MappedStatement getMappedStatement(String statementId){
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        return mappedStatement;
    }

    @Override
    public <T> T getMapper(Class<?> mapperInterfaceClass) {
        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperInterfaceClass}, new InvocationHandler() {
            //proxy 为当前代理对象
            //method 当前被调用方法的引用
            //args 传递的参数信息
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //底层还是去执行JDBC代码，只是我们需要根据不同情况，来选择调用selectList或是selectOne方法，如果是查询
                //我们可以根据返回值类型是否进行了 泛型类型参数化（通俗讲就是判断返回值是否是一个泛型）进行判断，如果是就
                //说明当前返回对象是一个集合，则调用selectList方法，否则就调用selectOne

                //1、准备参数statementId，在xml文件中statementId是由namespace.id来组成，但是我们此处只能获取到对应的
                //接口信息，并不能获取到xml文件，所以此处约定让namespace.id = 接口全限定名.方法名的方式来进行处理，这
                //也是Mybatis的Mapper.xml文件中namespace为什么要和接口路径保持一致的原因

                //获取方法名称
                String methodName = method.getName();
                //通过方法获取到接口类的全限定名
                String className = method.getDeclaringClass().getName();
                //拼接生成statementId
                String statementId = className+"."+methodName;


                //2、根据被调用方法的返回值类型，判断应该调用selectOne还是selectList方法
                Type genericReturnType = method.getGenericReturnType();
                if(genericReturnType instanceof ParameterizedType){
                    return selectList(statementId,args);
                } else {
                    //如果返回的结果集不是一个泛型，我们就通过MappedStatement对象中的SQL语句进行以什么开头来进行判断
                    MappedStatement mappedStatement = getMappedStatement(statementId);
                    //为避免SQL大小写差异统一将SQL语句转换成小写
                    String sql = mappedStatement.getSql().toLowerCase();
                    if(sql.startsWith("delete")){
                        return delete(statementId,args);
                    } else if(sql.startsWith("select")){
                        return selectOne(statementId,args);
                    } else {
                        return update(statementId,args);
                    }
                }
            }
        });
        return (T) proxyInstance;
    }
}
